package com.lin.missyou.core.interceptors;

import com.auth0.jwt.interfaces.Claim;
import com.lin.missyou.core.LocalUser;
import com.lin.missyou.exception.http.ForbiddenException;
import com.lin.missyou.exception.http.UnAuthException;
import com.lin.missyou.model.User;
import com.lin.missyou.service.UserService;
import com.lin.missyou.util.JwtToken;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import sun.reflect.generics.scope.Scope;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
import java.util.Optional;

/**
 * filter interceptor aop
 * 拦截器
 * 1、获取到请求token
 * 2、验证token
 * 3、scope
 * 4、读取API @ScopeLevel level
 * 5、scope ? level
 * @author gcq
 * @Create 2021-10-31
 */
public class PermissionInterceptor implements HandlerInterceptor     {

    @Autowired
    private UserService userService;
    /**
     *
     * @param request
     * @param response
     * @param handler 要访问的方法
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1、获取访问的api上的注解
        Optional<ScopeLevel> scopeLevel = this.getScopeLevel(handler);
        if(!scopeLevel.isPresent()){
            return true;
        }
        String bearerToken = request.getHeader("Authorization");
        if(StringUtils.isEmpty(bearerToken)){
            throw new UnAuthException(10004);
        }
        if(!bearerToken.startsWith("Bearer")){
            throw new UnAuthException(10004);
        }
        // 防止空数组
        String[] tokens = bearerToken.split(" ");
        if(!(tokens.length == 2)){
            throw new UnAuthException(10004);
        }
        Optional<Map<String, Claim>> OptionalMap = JwtToken.getClaims(tokens[1]);

        Map<String, Claim> map = OptionalMap.orElseThrow(() -> new UnAuthException(10004));
        // 保存当前线程用户信息
        this.setToThreadLocal(map);
        boolean valid = this.hasPermission(scopeLevel.get(), map);
        return valid;
    }

    /**
     * 权限验证
     * @param scopeLevel
     * @param map
     * @return
     */
    public boolean hasPermission(ScopeLevel scopeLevel, Map<String, Claim> map)
    {
        // 要访问的 api 的层级
        Integer level = scopeLevel.value();
        // token 中的层级
        Integer scope = map.get("scope").asInt();
        if(level > scope) {
            throw new ForbiddenException(10005);
        }
        return true;
    }
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    /**
     * 线程结束
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 释放线程资源
        LocalUser.clear();
    }


    /**
     * 获取 ScopeLevel 注解
     * @param handler
     * @return
     */
    public Optional<ScopeLevel> getScopeLevel(Object handler)
    {
        if(handler instanceof HandlerMethod)
        {
            HandlerMethod handlerMethod = (HandlerMethod)handler;
            ScopeLevel scopeLevel = handlerMethod.getMethod().getAnnotation(ScopeLevel.class);
            if(scopeLevel == null){
                return Optional.empty();
            }
            return Optional.of(scopeLevel);
        }
        return Optional.empty();
    }

    /**
     * 获取用户信息
     *
     * ThreadLocal 线程
     * @param map
     */
    private void setToThreadLocal(Map<String, Claim> map)
    {
        Claim uid = map.get("uid");
        Claim scope = map.get("scope");
        User user = userService.getUserById(uid.asLong());
        LocalUser.setUser(user, scope.asInt());
    }
}